import { Singleton } from "./core/Class";
import { EventConst } from "./core/EventConst";
import AudioMgr from "./core/AudioMgr";
import AvatarMgr from "./core/AvatarMgr";
import UserEvent from "./core/Event";
import UtilFun from "./core/UtilFun";
import NativeLib from "./core/Native";
import AssetsManager from "./core/AssetsMgr";
import StringUtil from "./core/StringUtil";
import shake from "./component/Shake";
import CSVFile from "./core/CSV";
import Map from "./core/Map";
interface LogInfo {
    method: string;
    path: string;
    line: string;
    pos: string;
    file: string;
};

class CApp extends Singleton<CApp>{
    private _isDebug:boolean = false;
    private _isWideScreen:boolean = false;
    private _logout:boolean = false;
    private _module:string = '';
    private _config:any = null;
    private _localData:any = null;
    private _localKey:string = '';
    private _language:string = '';
    private _aseKey:string = '';
    private _svrList:string[] = [];
    private _lang:Map<string,string> = new Map<string,string>();
    constructor(){
        super();
        
        this.Event.init();
        this._localData = {
            local_audio:{
                sound:1,
                music:1
            },
            local_login:{
                lastTime:0,
                loginType:0, // BaseModule.LoginType
                token:'',
                svrUrl:''
            },
            local_set:{
                sound:1,
                style:1,
            },
        };
    }

    protected onInit(config:string,lang:string,cb:(is:boolean)=>void){
        this.Res.init();
        this.Audio.init();

        //  加载配置
        this.Res.loadResources(config,cc.Asset,(json:cc.JsonAsset)=>{
            let Cfg = json.json;
            this._module = Cfg.module;
            this.Native.init();

            const localCfg = this.Native.getStringFromFile(`${config}.json`);
            localCfg && (Cfg = JSON.parse(localCfg));
            this._config = Cfg;
            this._module = Cfg.module;
            this._language = Cfg['modules'][`${this._module}`]['language'];
            this._isDebug = Cfg.debug;
            this._localKey = Cfg.build.packName;
            let _str = null;
            _str = cc.sys.localStorage.getItem(this._localKey);
            _str && (this._localData = JSON.parse(this.String.decode64(_str)));

            // 加载语言包
            this.CSV.load(lang,(data:any[])=>{
                if (data){
                    for (let it of data){
                        this._lang.inset(it.key,it[this._language]);
                    }
                }
                App.Res.setMainPack(App.ModuleName,cb);
            });
        });

        // 重启的时候必须清理下
        this.Event.on(EventConst.APP_RESTART,()=>{
            this.Res.destroy();
            this.Audio.destroy();
            this.Event.destroy();
        },this);
        
    }

    public setLocalStorage(data:any,key?:string){
        if (!key || key.length < 0) 
            this._localData = data;
        else this._localData[key] = data;
        let _str = this.String.encode64(this._localData);
        cc.sys.localStorage.setItem(this._localKey,_str);
    }

    public getLocalStorage(key?:string):any{
        return (key && key.length > 0)?this._localData[key]:this._localData;
    }
    
    /**
     * 获取所有配置
     */
    public get Config(){return this._config;}

    public MB_FLAG = {
        OK: 0,
        CANCEL: 1
    };

    public MOD_NAME = {
        MASTER:'master', // 框架
        HALL:'sub_hall', // 子游戏除了房间外的所有
        ROOM:'sub_room', // 子游戏房间   
    };

    // 本地记录信息key
    public LOCAL_KEY = {
        AUDIO:'local_audio',
        LOGIN:'local_login',
        SET:'local_set',
        USER:'local_user',
        WRITE_DIR:'native_write_path' // 本地写目录，热更使用
    };

    /**
     * 获取模块名
     */
    public get ModuleName():string{return this._module;}
    public set AseKey(v:string){this._aseKey = v;}
    public get AseKey():string{return this._aseKey;}
    public set SvrList(v:string[]){this._svrList=v;}
    public get SvrList():string[]{return this._svrList};
    public get isDebug():boolean{return this._isDebug;}
    public get isWideScreen():boolean{return this._isWideScreen;}
    public set wideScreen(is:boolean){this._isWideScreen = is;}
    public get writePath():string{ return  this.Native.getWritablePath(); }
    public get wideScreenOffset():number{return 100;}
    public get language():string{return this._language;}

    public set logout(is:boolean){this._logout = is;}
    public get isLogout():boolean{return this._logout;}

    /**
     * 获取资源管理系统
     */
    public get Res():AssetsManager{ return AssetsManager.getInstance<AssetsManager>(AssetsManager); }

    public get Audio():AudioMgr{return AudioMgr.getInstance<AudioMgr>(AudioMgr);}

    public get Avatar():AvatarMgr{return AvatarMgr.getInstance<AvatarMgr>(AvatarMgr);}

    public get Event():UserEvent{return UserEvent.getInstance<UserEvent>(UserEvent);}

    public get Util():UtilFun{return UtilFun.getInstance<UtilFun>(UtilFun);}

    public get Native():NativeLib{return NativeLib.getInstance<NativeLib>(NativeLib);}

    public get String():StringUtil{return StringUtil.getInstance<StringUtil>(StringUtil);}

    public get CSV():CSVFile{return CSVFile.getInstance<CSVFile>(CSVFile);}

    /**
     * 
     * @param key assets/resources/config.json文件key的路径 eg:'RunFast/launchScene'
     * @returns any
     */
    public getCfg(key?:string):any{
        if (!key || key === '') return this._config;
        let _paths = key.split('/');

        let _val:any = this._config[_paths.shift()];
        while (_paths.length > 0) {
            if (!_val) return _val;
            _val = _val[_paths.shift()];
        }

        // 如果没找到就去subModules/${sub}/res/config.json文件中查找
        if (this.Util.isInvalid(_val)){
            _paths = key.split('/');
            _paths.unshift('modules',`${this._module}`);
            _val = this._config[_paths.shift()];
            while (_paths.length > 0) {
                if (!_val) return _val;
                _val = _val[_paths.shift()];
            }
        }

        if (this.Util.isInvalid(_val)){
            const not = `WARN:App.getCfg(${key} or modules/${this._module}/${key}) is invalid.`;
            console.warn(not);
        }

        return _val;
    }

    /**
     * 获取多语言文本
     * @param key subGames/
     * @returns 
     */
    public getText(key:string):string{
        return this._lang.find(key);
        if (!key || key.length < 0) return '';

        let _path = `text/${key}/${this._language}`;
        let _str = this.getCfg(_path);
        if (!_str){
            _path = `modules/${this._module}/text/${key}/${this._language}`;
            _str = this.getCfg(_path);
            if (this.Util.isInvalid(_str)){
                console.error(`App.getText(${_path}) error.`);
                return '';
            }
        }
        return _str;
    }

    public setLableText(parent:cc.Node|cc.Component,path:string,str:string,sub:string='...',maxlen:number=-1){
        this.setLableStr(this.Util.find<cc.Label>(path,parent,'cc.Label'),str,sub,maxlen);
    }

    public setLableStr(lbl:cc.Label,str:string,sub:string='...',maxlen:number=-1){
        if (this.Util.isInvalid(lbl) || this.Util.isEmpty(str)) return ;
        lbl.string = (maxlen > 0 && str.length > maxlen) ? str.substring(0,maxlen-1)+sub : str;
    }

    /**
     * 把子模块的配置附加进resources/config.json文件中
     * @param json 
     */
    public addSubCfg(json:any){
        if (!json) return ;
        !this._config['modules'][this._module] && (this._config['modules'][this._module] = {});
        this.Util.mergeObject(this._config['modules'][this._module],json); 
    }

    public send(data:any,protocol?:string,bindCall?:(data:any)=>void){
        this.Event.emit(EventConst.SOCKET_SEND,data,protocol,bindCall);
    }

    /**
     * 打开一个窗口
     *
     * @param {cc.Component|cc.Node|string} name 窗体预制体的路径xx/yy:common|hall|room即路劲:标识
     * @param {...any[]} param
     * @memberof Application
     * 存放路径assets/subModules/ModuleName/res/prefab/view/xxx.prefab
     */
    public openView(view: cc.Component | cc.Node | string, openCall?: () => void, ...param: any) {
        this.Event.emit(EventConst.OPEN_VIEW, view, openCall, param);
    }

    /**
     *
     *
     * @static
     * @param {(cc.Component|cc.Node)} view 窗体对象
     * @param {()=>void} [closeCall] 关闭回调
     * @param {...any[]} param
     * @memberof Application
     */
    public closeView(view: cc.Component | cc.Node, closeCall?: () => void, ...args: any) {
        this.Event.emit(EventConst.CLOSE_VIEW, view, closeCall, args);
    }

    public justNode(target:cc.Node|cc.Component){
        this.Util.isValid(target) && this.Event.emit(EventConst.JUST_NODE_SIZE, target);
    }

    /**
     * 进入场景
     *
     * @static
     * @param {string} scene 场景预制体路径 xx/yy:common|hall|room即路劲:标识
     * @param {()=>void} [entryCall] 进入成功回调
     * @param {...any[]} param 
     * @memberof Application
     * 存放路径assets/subModules/ModuleName/res/prefab/scene/xxx.prefab
     */
    public runScene(scene: string, entryCall?: () => void, ...args: any) {
        this.Event.emit(EventConst.ENTRY_SCENE, scene, entryCall, args);
    }

    /**
     * 显示tips消息
     *
     * @static
     * @param {string} text
     * @memberof Application
     */
    public popTips(text: string) {
        if (!text) return;
        text = text.replace(/^\s*(.*?)[\s\n]*$/g, '$1');
        text.length > 0 && this.Event.emit(EventConst.PUSH_TIPS, text);
    }

    /**
     * 常规对话框
     *
     * @static
     * @param {string} text 显示内容
     * @param {string} [btnTitles] 按钮上的文本'YES|NO'表示创建显示‘YES’和‘NO’的两个操作按钮，'确定'表示只显示一个有‘确定’的操作按钮
     * @param {(flag:number)=>void} [call] 对话框关闭回调，0=yes,1=no
     * @param {string} [title] 对话框标题
     * @memberof Application
     */
    /**
     * 
     * @param text 显示文本|标题|按钮1|按钮2 eg:'购买|确定花费XXX购买YYY吗?|购买|取消'  '错误|链接服务器失败|确定'
     * @param call 点击按钮回调 App.MB_FLAG
     * @returns 
     */
    public MessageBox(text: string, call?: (flag: number) => void) {
        if (!text) return;
        text = text.replace(/^\s*(.*?)[\s\n]*$/g, '$1');
        text.length > 0 && this.Event.emit(EventConst.POP_MSG_BOX, text, call);
    }

    public http(url:string,data:string|Object,isGet:boolean=true,call?:(is:boolean,data:any)=>void,timeout?:number,reqCount?:number){
        if (!url || (url.indexOf('http://') < 0 && url.indexOf('https://') < 0)) return ;
        call = call || function(is:boolean,data:any){};
        timeout = timeout || 5000;
        reqCount = reqCount < 1 ? 3:reqCount; 

        let xhr = new XMLHttpRequest();
        xhr.timeout = 5000; // 超过5秒按当未连接处理
        let _reqCount = 0;

        if (data){
            if (data instanceof Object){
                let paramURL: string = '';
                for (let key in data) paramURL += `${key}=${data[key]}&`;
                if (paramURL.length > 1) data = `${paramURL.substring(0, paramURL.length - 1)}`;
            }
            
            url = `${url}?${data}` ;
        }

        let _request = () => {
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4){
                    if (xhr.status === 200){
                        call(true,xhr.responseText);
                    } else if (xhr.status === 404){
                        call(false,null);
                    }
                }
            };

            xhr.onerror = (e) => {
                call(false,e ? e.toString() : 'connect error');
            };

            xhr.ontimeout = (e)=>{
                if (_reqCount++ < reqCount) {
                    xhr.abort();
                    _request();
                }
                call(false,e ? e.toString() : 'connect timeout');
            };

            xhr.open(isGet?'GET':'POST', url, true);
            !isGet && xhr.setRequestHeader("Content-Type", "application/json");

            if (!isGet)
                xhr.send(JSON.stringify(data));
            else
                xhr.send(null);
        };

        _request();
    }

    public shakeNode(target:cc.Node,duration:number,x:cc.Vec2|number, y?: number){
        let offX,offY;
        if (typeof(x) === 'number') offX = x;
        else if (typeof(y) === 'number')offY = y;
        else if (x instanceof cc.Vec2){
            offX = x.x;
            offY = x.y;
        }
        shake(duration,offX,offY).startWithTarget(target);
    }
}

export  const App:CApp = CApp.getInstance<CApp>(CApp); 
export enum UpdateState{
    skip = 0,    // 跳过
    newVer,      // 新版本
    wait,        // 等待
    already,     // 准备
    progress,    // 进度
    success,     // 成功
    fail,        // 失败
    error,       // 错误
    reinstall,   // 重装
};